Перейти к основному содержимому

5.02. Разработка своего API

Разработчику Архитектору

Разработка своего API

Выбор фреймворка для API на Python

Flask

Flask — это легковесный микрофреймворк, идеально подходящий для небольших проектов, прототипирования и обучения. Он предоставляет минимум встроенных функций, но благодаря своей гибкости позволяет подключать только те компоненты, которые действительно нужны. Для создания API на Flask достаточно определить маршруты с помощью декораторов и вернуть JSON-ответ с помощью функции jsonify.

Преимущества Flask:

  • Простота освоения.
  • Минимальный объем зависимостей.
  • Полный контроль над архитектурой.
  • Богатая экосистема расширений (Flask-RESTful, Flask-JWT, Flask-SQLAlchemy).

Недостатки:

  • Требуется больше ручной работы для реализации типичных функций (валидация, сериализация, пагинация).
  • Отсутствие встроенной поддержки автоматической документации.

FastAPI

FastAPI — современный фреймворк, ориентированный на высокую производительность и удобство разработки. Он основан на стандартах OpenAPI и JSON Schema, что позволяет автоматически генерировать интерактивную документацию (через Swagger UI и ReDoc). FastAPI использует аннотации типов Python для валидации входных данных и сериализации выходных, что делает код самодокументируемым и менее подверженным ошибкам.

Преимущества FastAPI:

  • Автоматическая валидация и сериализация.
  • Встроенная поддержка асинхронного кода.
  • Генерация документации «из коробки».
  • Высокая скорость выполнения благодаря использованию Starlette и Pydantic.

Недостатки:

  • Требует понимания аннотаций типов и асинхронного программирования.
  • Меньше готовых решений для сложных сценариев по сравнению с Django.

Django REST Framework (DRF)

Django REST Framework — расширение для Django, превращающее его в мощный инструмент для построения API. DRF предоставляет сериализаторы, представления, классы разрешений, аутентификации, пагинации и фильтрации. Он отлично подходит для крупных проектов, где уже используется Django в качестве основного фреймворка.

Преимущества DRF:

  • Глубокая интеграция с Django ORM.
  • Поддержка сложных бизнес-логик и отношений между моделями.
  • Готовые решения для аутентификации (Token, Session, OAuth2).
  • Расширяемость и зрелая экосистема.

Недостатки:

  • Более высокий порог входа.
  • Избыточность для простых API.
  • Зависимость от архитектуры Django.

Архитектура типичного API на Python

Независимо от выбранного фреймворка, структура проекта обычно включает следующие компоненты:

Маршрутизация — определение URL-путей и сопоставление их с обработчиками (функциями или классами). Каждый маршрут соответствует определённому ресурсу или действию.

Обработчики запросов — функции, которые принимают входные данные, вызывают бизнес-логику и формируют ответ. Они отвечают за проверку прав доступа, обработку ошибок и преобразование данных.

Сериализация и десериализация — процесс преобразования объектов Python в формат, пригодный для передачи по сети (обычно JSON), и обратно. Сериализаторы также выполняют валидацию входных данных.

Бизнес-логика — ядро приложения, содержащее правила обработки данных, взаимодействия с базой данных, вызова внешних сервисов. Эта часть должна быть максимально независимой от деталей реализации API.

Слой доступа к данным — чаще всего это ORM (Object-Relational Mapper), такой как SQLAlchemy (для Flask и FastAPI) или Django ORM. ORM позволяет работать с базой данных через объекты Python, избегая прямого написания SQL-запросов.

Обработка ошибок — централизованный механизм перехвата исключений и возврата стандартизированных сообщений об ошибках. Хороший API никогда не возвращает «голые» трассировки стека.

Аутентификация и авторизация — механизмы проверки личности пользователя и его прав на выполнение операций. Часто используются токены (JWT, Bearer), сессии или API-ключи.

Логирование и мониторинг — запись событий, запросов и ошибок для последующего анализа. Это критически важно для диагностики проблем в продакшене.


Практические шаги: создание первого API

Начнём с минимального, но рабочего примера на FastAPI — фреймворке, который сочетает простоту и мощь. Установка требует только одного пакета:

pip install fastapi uvicorn

Создадим файл main.py:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
return {"message": "Добро пожаловать в ваш первый API!"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}

Запуск сервера:

uvicorn main:app --reload

После запуска приложение будет доступно по адресу http://127.0.0.1:8000. Документация автоматически генерируется по путям /docs (Swagger UI) и /redoc (ReDoc). Это демонстрирует одно из ключевых преимуществ FastAPI: разработчик получает интерактивную документацию без дополнительных усилий.

Аналогичный пример на Flask выглядит так:

from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/")
def home():
return jsonify({"message": "Добро пожаловать в ваш первый API!"})

@app.route("/items/<int:item_id>")
def get_item(item_id):
return jsonify({"item_id": item_id})

Запуск:

FLASK_APP=main.py flask run

Flask не предоставляет встроенную документацию, но её можно добавить с помощью расширений вроде Flask-Swagger или Flasgger.

В Django REST Framework процесс немного дольше, поскольку требует настройки проекта, но он оправдан при работе с большими системами. После создания проекта и приложения, определяются модели, сериализаторы и представления. DRF позволяет использовать как функциональные, так и классовые представления, а также готовые generic-классы для стандартных операций (ListCreateAPIView, RetrieveUpdateDestroyAPIView и другие).


Работа с данными: модели, ORM и сериализация

Большинство API взаимодействуют с базой данных. В Python это чаще всего делается через ORM — объектно-реляционный маппер, который отображает строки таблиц на объекты Python.

В FastAPI популярным выбором является SQLAlchemy в связке с Pydantic. Pydantic отвечает за валидацию и сериализацию, а SQLAlchemy — за запросы к базе. Пример модели пользователя:

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()
engine = create_engine("sqlite:///./test.db")
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
email = Column(String, unique=True, index=True)

Pydantic-схема для входных и выходных данных:

from pydantic import BaseModel

class UserCreate(BaseModel):
name: str
email: str

class UserResponse(BaseModel):
id: int
name: str
email: str

class Config:
from_attributes = True # заменяет orm_mode в новых версиях

Обработчик создания пользователя:

from fastapi import Depends, HTTPException

def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

@app.post("/users/", response_model=UserResponse)
def create_user(user: UserCreate, db: SessionLocal = Depends(get_db)):
db_user = db.query(User).filter(User.email == user.email).first()
if db_user:
raise HTTPException(status_code=400, detail="Email уже зарегистрирован")
new_user = User(name=user.name, email=user.email)
db.add(new_user)
db.commit()
db.refresh(new_user)
return new_user

Этот код демонстрирует сквозной поток: клиент отправляет JSON → FastAPI валидирует его через Pydantic → бизнес-логика проверяет уникальность → данные сохраняются через SQLAlchemy → ответ сериализуется обратно в JSON.

В Django REST Framework этот процесс инкапсулирован ещё глубже. Сериализаторы наследуются от serializers.ModelSerializer, а представления — от generics.ListCreateAPIView и подобных. Это ускоряет разработку, но требует понимания внутренней архитектуры фреймворка.


Обработка ошибок и стандартизация ответов

Хороший API возвращает структурированные ошибки. Например, вместо простого текста "Invalid email" клиент получает:

{
"error": {
"code": "invalid_email",
"message": "Формат электронной почты некорректен",
"field": "email"
}
}

В FastAPI это достигается через пользовательские исключения и обработчики:

from fastapi import HTTPException
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return JSONResponse(
status_code=400,
content={"error": {"code": "validation_failed", "message": str(exc)}}
)

В Flask используется декоратор @app.errorhandler, в DRF — настройка EXCEPTION_HANDLER в конфигурации.

Стандартизация формата ответов (включая успешные) повышает предсказуемость. Многие команды используют обёртку вида:

{
"data": { ... },
"meta": { "timestamp": "...", "version": "1.0" }
}

или

{
"success": true,
"result": { ... }
}

Такой подход особенно полезен при интеграции с фронтендом или мобильными приложениями.


Аутентификация и безопасность

API должен защищать данные от несанкционированного доступа. Наиболее распространённые методы:

  • API-ключи — простой способ идентификации клиента. Передаются в заголовке X-API-Key.
  • JWT (JSON Web Token) — токен, содержащий закодированную информацию о пользователе и сроках действия. Подписывается секретным ключом, что гарантирует подлинность.
  • OAuth2 — стандарт делегированного доступа, часто используемый для авторизации через сторонние сервисы (Google, GitHub).

FastAPI предоставляет встроенные зависимости для всех этих сценариев. Например, защита эндпоинта с помощью Bearer-токена:

from fastapi.security import HTTPBearer
from fastapi import Depends

security = HTTPBearer()

@app.get("/protected")
def protected_route(token: str = Depends(security)):
# Проверка токена
return {"message": "Доступ разрешён"}

Важно также применять общие меры безопасности:

  • Ограничение частоты запросов (rate limiting).
  • Валидация всех входных данных.
  • Использование HTTPS в продакшене.
  • Отключение детальных сообщений об ошибках в production-среде.

Тестирование API

Тестирование — неотъемлемая часть жизненного цикла API. Python предлагает несколько уровней:

  • Unit-тесты — проверяют отдельные функции и компоненты (например, сериализаторы).
  • Интеграционные тесты — проверяют взаимодействие между слоями (например, вызов эндпоинта с последующей проверкой состояния базы данных).
  • End-to-end тесты — имитируют реальные запросы от клиента.

FastAPI поддерживает тестирование через TestClient:

from fastapi.testclient import TestClient

client = TestClient(app)

def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Добро пожаловать в ваш первый API!"}

Flask использует test_client(), DRF — APIClient из rest_framework.test.

Автоматизированные тесты должны запускаться при каждом коммите, что обеспечивается CI/CD-системами (GitHub Actions, GitLab CI и другими).


Документирование API

Документация — это не дополнение, а обязательная часть любого публичного или внутреннего API. Без неё интеграция становится медленной, ошибки — частыми, а поддержка — затратной. Современные фреймворки Python позволяют генерировать документацию автоматически, основываясь на аннотациях кода.

OpenAPI (ранее Swagger) — это стандарт описания RESTful API. Он определяет структуру запросов, параметров, тел, ответов, схем данных и возможных ошибок. FastAPI генерирует спецификацию OpenAPI в реальном времени и предоставляет два интерфейса для её просмотра:

  • Swagger UI (/docs) — интерактивный веб-интерфейс, где можно отправлять запросы прямо из браузера.
  • ReDoc (/redoc) — более читаемый, документоориентированный просмотрщик.

В Flask документацию можно добавить с помощью расширений:

  • Flasgger — генерирует OpenAPI-спецификацию на основе docstring’ов.
  • apispec — более гибкий инструмент, совместимый с Marshmallow.

В Django REST Framework используется модуль drf-yasg (Yet Another Swagger Generator) или встроенный SchemaGenerator, который работает с классами представлений и сериализаторами.

Хорошая документация содержит:

  • Описание каждого эндпоинта: назначение, примеры использования.
  • Спецификацию всех параметров: тип, обязательность, формат, допустимые значения.
  • Примеры успешных и ошибочных ответов.
  • Информацию об аутентификации.
  • Схемы данных в формате JSON Schema.

Автоматическая генерация гарантирует, что документация всегда соответствует текущему состоянию кода. Это особенно важно при частых изменениях.


Версионирование API

Со временем требования к API меняются: добавляются новые поля, удаляются старые, изменяется логика. Чтобы не нарушать работу существующих клиентов, применяется версионирование.

Наиболее распространённые подходы:

  1. Версия в URL:
    GET /v1/users
    GET /v2/users
    Этот метод прост, нагляден и легко кэшируется. Он рекомендован большинством крупных платформ (GitHub, Stripe).

  2. Версия в заголовке запроса:
    Accept: application/vnd.myapi.v2+json
    Этот способ сохраняет чистоту URL, но сложнее для отладки и требует явной настройки на стороне клиента.

  3. Версия в параметре строки запроса:
    GET /users?version=2
    Такой подход менее предпочтителен, так как нарушает принцип идемпотентности и усложняет кэширование.

При проектировании API следует заранее предусмотреть возможность версионирования. Например, в FastAPI можно создать отдельные подприложения для каждой версии:

from fastapi import FastAPI

app = FastAPI()
v1 = FastAPI()
v2 = FastAPI()

app.mount("/v1", v1)
app.mount("/v2", v2)

Каждая версия развивается независимо, но может использовать общие модели и бизнес-логику через импорты.


Развёртывание API

Локальный запуск — лишь первый шаг. Для продакшена требуется надёжное развёртывание.

WSGI vs ASGI

  • Flask и Django работают по протоколу WSGI (Web Server Gateway Interface). Для них используются серверы: Gunicorn, uWSGI.
  • FastAPI и современные асинхронные приложения используют ASGI (Asynchronous Server Gateway Interface). Серверы: Uvicorn, Daphne, Hypercorn.

Типичный стек для FastAPI в production:

  • Uvicorn — ASGI-сервер.
  • Gunicorn — менеджер процессов, запускающий несколько воркеров Uvicorn.
  • Nginx — обратный прокси, обрабатывающий SSL, статические файлы, ограничение скорости.
  • Docker — контейнеризация для единообразного окружения.

Пример Dockerfile для FastAPI:

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "main:app"]

Для Flask аналогичный файл использует gunicorn main:app.

Облачные платформы
API можно развернуть на:

  • Render, Fly.io, Railway — простые PaaS-решения с минимальной конфигурацией.
  • AWS Lambda + API Gateway — бессерверный подход (serverless), подходит для событийных или редко вызываемых API.
  • Google Cloud Run, Azure Container Instances — запуск контейнеров без управления инфраструктурой.

Выбор зависит от нагрузки, бюджета и требований к масштабируемости.


Мониторинг и логирование

В продакшене API должен быть наблюдаемым. Это достигается через:

  • Логирование — запись входящих запросов, исключений, времени выполнения. В Python используется модуль logging. Логи направляются в файлы, систему типа ELK (Elasticsearch, Logstash, Kibana) или облачные сервисы (CloudWatch, Datadog).
  • Метрики — количество запросов, время ответа, частота ошибок. Инструменты: Prometheus + Grafana.
  • Трассировка — отслеживание цепочки вызовов в распределённой системе. Используется OpenTelemetry.

FastAPI и Flask легко интегрируются с этими системами через middleware — промежуточные слои, которые перехватывают каждый запрос и ответ.

Пример middleware для логирования в FastAPI:

import time
from fastapi import Request

@app.middleware("http")
async def log_requests(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
print(f"{request.method} {request.url.path}{response.status_code}{process_time:.3f}s")
return response

Такой подход позволяет собирать данные без изменения основной логики приложения.


Лучшие практики проектирования API

  1. Используйте существительные, а не глаголы в путях: /users, а не /getUsers.
  2. Соблюдайте иерархию ресурсов: /users/123/orders/456.
  3. Возвращайте полезные HTTP-статусы: не используйте 200 OK для ошибок.
  4. Ограничьте размер ответа: применяйте пагинацию (limit, offset или курсорную).
  5. Поддерживайте частичное обновление: PATCH вместо полной замены через PUT.
  6. Избегайте вложенных JSON-структур глубже двух уровней — это усложняет парсинг.
  7. Предоставляйте ссылки на связанные ресурсы: { "user": "/users/123" }.
  8. Не возвращайте чувствительные данные (пароли, токены) в ответах.
  9. Поддерживайте CORS, если API вызывается из браузера.
  10. Пишите тесты до или одновременно с реализацией — это гарантирует стабильность контракта.